package cn.lili.modules.token.impl;

import cn.hutool.core.text.CharSequenceUtil;
import cn.lili.cache.Cache;
import cn.lili.cache.CachePrefix;
import cn.lili.common.context.ThreadContextHolder;
import cn.lili.common.enums.ClientTypeEnum;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.security.AuthUser;
import cn.lili.common.security.context.UserContext;
import cn.lili.common.security.enums.PermissionEnum;
import cn.lili.common.security.enums.SceneEnums;
import cn.lili.common.security.token.Token;
import cn.lili.common.security.token.TokenUtil;
import cn.lili.common.utils.DateUtil;
import cn.lili.common.utils.SnowFlake;
import cn.lili.common.utils.StringUtils;
import cn.lili.exchange.AmqpExchangeProperties;
import cn.lili.modules.member.client.UserClient;
import cn.lili.modules.member.client.VipClient;
import cn.lili.modules.member.entity.dos.User;
import cn.lili.modules.member.entity.enums.QRCodeLoginSessionStatusEnum;
import cn.lili.modules.member.entity.vo.QRCodeLoginSessionVo;
import cn.lili.modules.member.entity.vo.QRLoginResultVo;
import cn.lili.modules.permission.client.PermissionClient;
import cn.lili.modules.permission.entity.vo.UserMenuVO;
import cn.lili.modules.system.entity.dos.LiVip;
import cn.lili.modules.token.TokenGenerate;
import cn.lili.modules.token.extend.TokenGenerateExtend;
import cn.lili.routing.UserRoutingKey;
import cn.lili.util.AmqpMessage;
import cn.lili.util.AmqpSender;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * token 生成实现
 *
 * @author Chopper
 * @version v1.0
 * 2022-08-29 16:38
 */
@Service
@RequiredArgsConstructor
public class TokenGenerateImpl implements TokenGenerate {


    private final TokenUtil tokenUtil;

    private final PermissionClient permissionClient;

    private final UserClient userClient;

    private final VipClient vipClient;

    private final AmqpExchangeProperties amqpExchangeProperties;

    private final Cache cache;

    private final List<TokenGenerateExtend> tokenGenerateExtends;

    private final AmqpSender amqpSender;

    @Override
    public Token generateToken(User user) {
        return generateToken(user, false);
    }

    @Override
    public Token generateToken(User user, Boolean longTerm) {
        if (Boolean.FALSE.equals(user.getEnable())) {
            throw new ServiceException(ResultCode.USER_STATUS_ERROR);
        }
        //获取客户端类型
        String clientType = ThreadContextHolder.getHttpRequest().getHeader("clientType");
        ClientTypeEnum clientTypeEnum;
        try {
            //如果客户端为空，则缺省值为PC，pc第三方登录时不会传递此参数
            if (clientType == null) {
                clientTypeEnum = ClientTypeEnum.PC;
            } else {
                clientTypeEnum = ClientTypeEnum.valueOf(clientType);
            }
        } catch (IllegalArgumentException e) {
            clientTypeEnum = ClientTypeEnum.UNKNOWN;
        }
        LiVip liVip = vipClient.getByUserId(user.getId());
        //记录最后登录时间，客户端类型
        user.setLastLoginDate(new Date());
        user.setClientEnum(clientTypeEnum.name());
        AuthUser authUser = AuthUser.builder()
                .username(user.getUsername())
                .id(user.getId())
                .nickName(user.getNickName())
                .face(user.getFace())
                .scene(SceneEnums.valueOf(user.getScene()))
                .extendId(user.getExtendId())
                .extendName(user.getExtendName())
                .isSuper(user.getIsSuper())
                .openId(user.getOpenId())
                .isVip(liVip != null && liVip.getEndTime() != null && liVip.getEndTime().getTime() > new Date().getTime() && Integer.valueOf(1).equals(liVip.getVipStatus()))
                .build();

        this.handlerPermission(authUser);

        //登陆成功生成token
        Token token = tokenUtil.createToken(authUser, longTerm);

        //发送登录消息
        loginMessageSend(authUser);

        return token;
    }


    @Override
    public Token refreshToken(String refreshToken) {
        AuthUser authUser = tokenUtil.generate(refreshToken);
        if (authUser!=null && StringUtils.isNotEmpty(authUser.getId())) {
            LiVip liVip = vipClient.getByUserId(authUser.getId());
            authUser.setIsVip(liVip != null && liVip.getEndTimeTow() != null && liVip.getEndTimeTow().getTime() > new Date().getTime() && Integer.valueOf(1).equals(liVip.getVipStatus()));
        }
        //刷新token操作
        Token token = tokenUtil.refreshToken(refreshToken, authUser);

        //发送登录消息
        loginMessageSend(tokenUtil.generate(refreshToken));

        return token;
    }

    /**
     * 获取用户权限
     *
     * @param authUser 权限菜单集合
     * @return 权限MAP
     */
    public void handlerPermission(AuthUser authUser) {
        //定义默认权限模型
        Map<String, List<String>> permissionMap;
        //如果场景非会员，则对对应场景做权限处理。
        //如果会员也需要额外的权限控制，则这里需要继续重构。权限写入目前都放在扩展实现类中
        if (!authUser.getScene().equals(SceneEnums.MEMBER)) {

            //获取用户权限
            List<UserMenuVO> userMenuVOList = permissionClient.getUserPermission(authUser.getId());

            //生成默认操作权限以及查询权限
            permissionMap = generatePermission(userMenuVOList);

            //权限扩展 对应角色生成默认权限列表
            for (TokenGenerateExtend tokenGenerateExtend : tokenGenerateExtends) {
                tokenGenerateExtend.extend(authUser, permissionMap);
            }
            //缓存权限列表
            cache.put(tokenUtil.generatePermissionCacheKey(authUser), permissionMap);

        }
    }

    @Override
    public QRCodeLoginSessionVo createPcSession() {
        QRCodeLoginSessionVo session = new QRCodeLoginSessionVo();
        session.setStatus(QRCodeLoginSessionStatusEnum.WAIT_SCANNING.getCode());
        //过期时间，20s
        Long duration = 20 * 1000L;
        session.setDuration(duration);
        String token = CachePrefix.QR_CODE_LOGIN_SESSION.name() + SnowFlake.getIdStr();
        session.setToken(token);
        cache.put(token, session, duration, TimeUnit.MILLISECONDS);
        return session;
    }

    @Override
    public Object appScanner(String token) {
        AuthUser tokenUser = UserContext.getCurrentUser();
        if (tokenUser == null) {
            throw new ServiceException(ResultCode.USER_NOT_LOGIN);
        }
        QRCodeLoginSessionVo session = (QRCodeLoginSessionVo) cache.get(token);
        if (session == null) {
            return QRCodeLoginSessionStatusEnum.NO_EXIST.getCode();
        }
        session.setStatus(QRCodeLoginSessionStatusEnum.SCANNING.getCode());
        cache.put(token, session, session.getDuration(), TimeUnit.MILLISECONDS);
        return QRCodeLoginSessionStatusEnum.SCANNING.getCode();
    }

    @Override
    public boolean appSConfirm(String token, Integer code) {
        AuthUser tokenUser = UserContext.getCurrentUser();
        if (tokenUser == null) {
            throw new ServiceException(ResultCode.USER_NOT_LOGIN);
        }
        QRCodeLoginSessionVo session = (QRCodeLoginSessionVo) cache.get(token);
        if (session == null) {
            return false;
        }
        if (code == 1) {
            //同意
            session.setStatus(QRCodeLoginSessionStatusEnum.VERIFIED.getCode());
            session.setUserId(Long.parseLong(tokenUser.getId()));
        } else {
            //拒绝
            session.setStatus(QRCodeLoginSessionStatusEnum.CANCELED.getCode());
        }
        cache.put(token, session, session.getDuration(), TimeUnit.MILLISECONDS);
        return true;
    }

    @Override
    public QRLoginResultVo loginWithSession(String token) {
        QRLoginResultVo result = new QRLoginResultVo();
        result.setStatus(QRCodeLoginSessionStatusEnum.NO_EXIST.getCode());
        QRCodeLoginSessionVo session = (QRCodeLoginSessionVo) cache.get(token);
        if (session == null) {
            return result;
        }
        result.setStatus(session.getStatus());
        if (QRCodeLoginSessionStatusEnum.VERIFIED.getCode().equals(session.getStatus())) {
            //生成token
            User member = userClient.getById(String.valueOf(session.getUserId()));
            if (member == null) {
                throw new ServiceException(ResultCode.USER_NOT_EXIST);
            } else {
                //生成token
                Token sessionToken = this.generateToken(member, false);
                result.setToken(sessionToken);
                cache.vagueDel(token);
            }

        }
        return result;
    }

    /**
     * 根据用户菜单对象获取用户权限
     *
     * @param userMenuVOList 用户菜单权限
     * @return 权限集合
     */
    private Map<String, List<String>> generatePermission(List<UserMenuVO> userMenuVOList) {
        Map<String, List<String>> permission = new HashMap<>(2);

        List<String> superPermissions = new ArrayList<>();
        List<String> queryPermissions = new ArrayList<>();
        //循环权限菜单
        if (userMenuVOList != null && !userMenuVOList.isEmpty()) {
            userMenuVOList.forEach(menu -> {
                //循环菜单，赋予用户权限
                if (CharSequenceUtil.isNotEmpty(menu.getPermission())) {
                    //获取路径集合
                    String[] permissionUrl = menu.getPermission().split(",");
                    //for循环路径集合
                    for (String url : permissionUrl) {
                        //如果是超级权限 则计入超级权限
                        if (Boolean.TRUE.equals(menu.getSuper())) {
                            //如果已有超级权限，则这里就不做权限的累加
                            if (!superPermissions.contains(url)) {
                                superPermissions.add(url);
                            }
                        }
                        //否则计入浏览权限
                        else {
                            //没有权限，则累加。
                            if (!queryPermissions.contains(url)) {
                                queryPermissions.add(url);
                            }
                        }
                    }
                }
                //去除重复的权限
                queryPermissions.removeAll(superPermissions);
            });
        }
        permission.put(PermissionEnum.SUPER.name(), superPermissions);
        permission.put(PermissionEnum.QUERY.name(), queryPermissions);
        return permission;
    }

    /**
     * 用户登录发送消息
     *
     * @param authUser 用户
     */
    private void loginMessageSend(AuthUser authUser) {
        amqpSender.send(
                AmqpMessage.builder()
                        .exchange(amqpExchangeProperties.getUser())
                        .routingKey(UserRoutingKey.USER_LOGIN)
                        .message(authUser)
                        .build()
        );
    }

}
